/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.mapping;

import static net.paoding.rose.impl.util.PlaceHolderUtils.PLACEHOLDER_INNER_PREFIX;
import static net.paoding.rose.impl.util.PlaceHolderUtils.PLACEHOLDER_INNER_PREFIX_STRING;
import static net.paoding.rose.impl.util.PlaceHolderUtils.PLACEHOLDER_PREFIX;
import static net.paoding.rose.impl.util.PlaceHolderUtils.PLACEHOLDER_SUFFIX;
import static net.paoding.rose.impl.util.PlaceHolderUtils.PLACEHOLDER_SUFFIX_CHAR;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.paoding.rose.web.annotation.ReqMethod;
import net.paoding.rose.web.impl.thread.MatchResult;

/**
 * 
 * @author zhiliang.wang
 * 
 * @param <T>
 */
public class PatternMapping<T> extends AbstractMapping<T> {

	protected int mode;

	private static final String[] EMPTY = new String[0];

	protected Pattern paramsPattern;
	protected List<String> constants = new ArrayList<String>(2);
	protected String[] paramNames = EMPTY;

	public PatternMapping(String path, ReqMethod[] methods, T target) {
		this(path, MappingFactory.MODE_EQUALS, methods, target);
	}

	public PatternMapping(String path, int mode, ReqMethod[] methods, T target) {
		super(path, methods, target);
		this.mode = mode;
		initPattern();
	}

	public MatchResult match(String path, String requestMethod) {
		if (!isRequestMethodSupported(requestMethod)) {
			return null;
		}
		Matcher matcher = paramsPattern.matcher(path);
		if (!matcher.find()) {
			return null;
		}
		MatchResult matchResult = new MatchResult();
		String string = matcher.group(0);
		while (string.endsWith("/")) {
			string = string.substring(0, string.length() - 1);
		}
		matchResult.setMatchedString(string);
		if (paramNames.length == 0) {
			return matchResult;
		}
		for (int i = 0; i < this.paramNames.length; i++) {
			matchResult.putParameter(paramNames[i], matcher.group(i + 1));
		}
		return matchResult;
	}

	protected void initPattern() {
		ArrayList<String> paramNames = new ArrayList<String>();
		int nextIndex = 0;
		String defaultRegex = "([[^/].]+)";
		String placeHolderPrefix = PLACEHOLDER_PREFIX;
		int startIndex = path.indexOf(placeHolderPrefix);
		if (startIndex == -1) {
			placeHolderPrefix = PLACEHOLDER_INNER_PREFIX_STRING;
			startIndex = path.indexOf(placeHolderPrefix);
		}
		if (startIndex == -1) {
			this.paramsPattern = compile(path);
		} else {
			int paramIndex = 1; // 记录参数的位置，从0开始，用于匿名参数转化为param1的形式
			StringBuilder buf = new StringBuilder(path);
			while (startIndex != -1) {
				int endIndex = -1;
				int cc = 0;
				for (int i = startIndex + placeHolderPrefix.length(); i < buf
						.length(); i++) {
					char ch = buf.charAt(i);
					if (ch == PLACEHOLDER_INNER_PREFIX) {
						cc++;
					} else if (ch == PLACEHOLDER_SUFFIX_CHAR) {
						cc--;
					}
					if (cc < 0) {
						endIndex = i;
						break;
					}
				}
				if (endIndex != -1) {
					String paramName = null;
					String regex = defaultRegex;
					for (int i = startIndex + placeHolderPrefix.length(); i < endIndex; i++) {
						if (buf.charAt(i) == ':') {
							paramName = buf.substring(startIndex
									+ placeHolderPrefix.length(), i);
							regex = buf.substring(i + 1, endIndex);
							if (regex.length() == 0) {
								regex = defaultRegex;
							} else if (regex.equals('*')) {
								regex = "(.*)";
							} else if (regex.equals('+')) {
								regex = "(.+)";
							} else if (regex.equals('?')) {
								regex = "(.?)";
							} else if (regex.charAt(0) != '(') {
								regex = '(' + regex + ')';
							}
							break;
						}
					}
					if (paramName == null) {
						paramName = buf.substring(startIndex
								+ placeHolderPrefix.length(), endIndex);
					}
					if (paramName.length() == 0) {
						paramName = "param" + paramIndex;
					}
					constants.add(buf.substring(nextIndex, startIndex));
					paramNames.add(paramName);
					buf.replace(startIndex, endIndex
							+ PLACEHOLDER_SUFFIX.length(), regex);
					nextIndex = startIndex + regex.length();
					startIndex = buf.indexOf(placeHolderPrefix, nextIndex);
					if (startIndex == -1) {
						placeHolderPrefix = PLACEHOLDER_INNER_PREFIX_STRING;
						startIndex = path.indexOf(placeHolderPrefix);
					}
					paramIndex++;
				} else {
					startIndex = -1;
				}
			}

			// 最后的constaint, 可能为空串
			constants.add(buf.substring(nextIndex));
			this.paramNames = paramNames.toArray(new String[paramNames.size()]);
			this.paramsPattern = compile(buf.toString());
		}
		if (constants.size() == 0) {
			constants.add(path);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("map '" + path + "' to pattern '"
					+ paramsPattern.toString() + "'");
			logger.debug("'" + path + "'.params="
					+ Arrays.toString(this.paramNames));
			logger.debug("'" + path + "'.constants=" + this.constants);
		}
	}

	protected Pattern compile(String pattern) {
		switch (mode) {
		case MappingFactory.MODE_STARTS_WITH:
			return Pattern.compile("^" + pattern + "($|/)");
		case MappingFactory.MODE_EQUALS:
			return Pattern.compile("^" + pattern + "/?$");
		default:
			throw new IllegalArgumentException("illegal mode: " + mode);
		}
	}

	@Override
	public int compareTo(Mapping<T> o) {
		if (!(o instanceof PatternMapping)) {
			return o.getPath().length() == 0 ? -1 : 1;
		}
		PatternMapping<T> pm = (PatternMapping<T>) o;
		// 变量个数比较多，排在前面
		if (paramNames.length > pm.paramNames.length) {
			return -1;
		}
		// 变量个数比较少，排在后面
		else if (paramNames.length < pm.paramNames.length) {
			return 1;
		}
		// 相等个数的，看分隔情况
		// ab${id}排在a${id}前面
		else {
			for (int i = 0; i < constants.size(); i++) {
				if (this.constants.get(i).equals(pm.constants.get(i))) {
					continue;
				}
				if (this.constants.get(i).length() == 0) {
					return 1;
				} else if (this.constants.get(i)
						.startsWith(pm.constants.get(i))) {
					return -1;
				} else if (pm.constants.get(i)
						.startsWith(this.constants.get(i))) {
					return 1;
				} else {
					return this.constants.get(i).compareTo(pm.constants.get(i));
				}
			}
		}
		return 0;
	}

}
